#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics.CodeAnalysis;
using C5;

namespace Grawlix.Services
{
	/// <summary>
	/// A simple mapping of domain names to factories which create
	/// them.
	/// </summary>
	[Serializable]
	public class MappedDomainProvider :
		ServiceBase<IDomainProvider>, IDomainProvider
	{
		/// <summary>
		/// The internal map of loaded domains.
		/// </summary>
		private readonly
			HashDictionary<string, ExternalProxy<Domain>> _domainMap
				= new HashDictionary<string, ExternalProxy<Domain>>();

		/// <summary>
		/// The domain factory for unlisted names.
		/// </summary>
		private readonly IDomainFactory _fallback;

		/// <summary>
		/// The name of the main controlling domain.
		/// </summary>
		private readonly string _mainName;

		/// <summary>
		/// The mapping from name to factory.
		/// </summary>
		private readonly HashDictionary<string, IDomainFactory>
			_mapping = new HashDictionary<string, IDomainFactory>();

		/// <summary>
		/// Initializes the mapping between name and factory.
		/// </summary>
		/// <param name="mapping">The mapping from name to factory.
		/// </param>
		/// <param name="fallback">The domain factory for unlisted
		/// names.</param>
		/// <param name="mainName">The main controlling domain.
		/// </param>
		/// <exception cref="ArgumentNullException"><c>fallback</c> is
		/// null.</exception>
		/// <exception cref="ArgumentNullException"><c>mapping</c> is
		/// null.</exception>
		[SuppressMessage("Microsoft.Design", "CA1006",
			Justification =
				"Just a more generic form of IDictionary<K,V>.")]
		public MappedDomainProvider(
			System.Collections.Generic.IDictionary
				<string, IDomainFactory> mapping,
			IDomainFactory fallback, string mainName)
		{
			if (fallback == null)
				throw new ArgumentNullException("fallback");
			if (mapping == null)
				throw new ArgumentNullException("mapping");
			if (mainName == null)
				throw new ArgumentNullException("mainName");
			foreach (var pair in mapping)
				_mapping[pair.Key.ToUpperInvariant()] = pair.Value;
			_fallback = fallback;
			_mainName = mainName;
		}

		/// <summary>
		/// Gets an immutable list of all the contained
		/// <see cref="Domain"/> objects.
		/// </summary>
		/// <value>The domains.</value>
		public IList<Domain> Domains
		{
			get
			{
				lock (_domainMap)
				{
					ICollectionValue<ExternalProxy<Domain>> proxies =
						_domainMap.Values;
					var domains = new ArrayList<Domain>(proxies.Count);
					foreach (var proxy in proxies)
						domains.Add(proxy.Target);
					return domains;
				}
			}
		}

		/// <summary>
		/// Gets the the main controlling domain.
		/// </summary>
		/// <returns>The name of the main controlling domain.</returns>
		public Domain Main
		{
			get
			{
				return Resolve(_mainName);
			}
		}

		/// <summary>
		/// Finds or loads the domain with the specified name.
		/// </summary>
		/// <param name="name">The name of the domain to resolve.
		/// </param>
		/// <returns>The domain, which may or not be ready.</returns>
		/// <exception cref="ArgumentNullException"><c>name</c> is
		/// null.</exception>
		public Domain Resolve(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");
			lock (_domainMap)
			{
				string upperName = name.ToUpperInvariant();
				if (_domainMap.Contains(upperName))
					return _domainMap[upperName].Target;
				IDomainFactory factory = _mapping.Contains(upperName)
				                         	? _mapping[upperName]
				                         	: _fallback;
				ExternalProxy<Domain> domain = factory.Create(name);
				_domainMap[upperName] = domain;
				return domain.Target;
			}
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the
		/// current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current
		/// <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			return "MappedDomainProvider " + _mapping + " fallback=" +
			       _fallback;
		}
	}
}